Skip to content

FIS getAuthToken implementation. #769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Sep 16, 2019
Merged

FIS getAuthToken implementation. #769

merged 31 commits into from
Sep 16, 2019

Conversation

ankitaj224
Copy link
Contributor

No description provided.

private static <F, T> Continuation<F, T> call(@NonNull Supplier<T> supplier) {
return t -> {
if (!t.isComplete()) {
Tasks.await(t);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ciarand Instrumented tests fail sometimes because of this line. Its not strictly waiting for the prev Task (getId) to complete. Ends up calling refreshAuthTokenIfNecessary with empty PersistedFidEntry ( pointed you to that code).

Can you please help me to understand what am I missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test failure is alternating.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct. You're calling Tasks.call(executor, this::getId) but getId is already calling Tasks.call so you're doubly nesting your async operations.

Instead of this:

    return Tasks.call(executor, this::getId)
        .continueWith(call(() -> refreshAuthTokenIfNecessary(forceRefresh)));

I think you mean to do something like this:

    return getId().continueWith((idTask) -> refreshAuthTokenIfNecessary(idTask.getResult(), forceRefresh));

NOTE: The call to getResult will throw an Exception if the getId task failed, which will cause the return value of this method (the getAuthToken method) to be a failed Task, which is afaict what we want.

You're also not passing an explicit executor into the .continueWith call though, so the refreshAuthTokenIfNecessary method is getting run on the main thread (!). Given you're not allowed to call Tasks.await on the main thread, that seems like a bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good catch. You are right. That was the issue :) Fixed it. About the 'Tasks.await' it wasn't a bug until I called Tasks.call(executor, this::getId) . After the change recommended by you, I could not await in the Main Thread. I updated the code to await using the executor. Let me know what you think.

private static <F, T> Continuation<F, T> call(@NonNull Supplier<T> supplier) {
return t -> {
if (!t.isComplete()) {
Tasks.await(t);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct. You're calling Tasks.call(executor, this::getId) but getId is already calling Tasks.call so you're doubly nesting your async operations.

Instead of this:

    return Tasks.call(executor, this::getId)
        .continueWith(call(() -> refreshAuthTokenIfNecessary(forceRefresh)));

I think you mean to do something like this:

    return getId().continueWith((idTask) -> refreshAuthTokenIfNecessary(idTask.getResult(), forceRefresh));

NOTE: The call to getResult will throw an Exception if the getId task failed, which will cause the return value of this method (the getAuthToken method) to be a failed Task, which is afaict what we want.

You're also not passing an explicit executor into the .continueWith call though, so the refreshAuthTokenIfNecessary method is getting run on the main thread (!). Given you're not allowed to call Tasks.await on the main thread, that seems like a bug.

2. Renaming InstallationTokenResult TokenExpirationTimestampMillis field to TokenExpirationInSecs
@ankitaj224 ankitaj224 requested a review from ciarand September 6, 2019 21:04
@@ -217,4 +225,22 @@ public void testGetId_PersistedFidError_BackendOk() throws InterruptedException
.isEqualTo(FirebaseInstallationsException.Status.CLIENT_ERROR);
}
}

@Test
public void testGetAuthToken_registeredFid_successful() throws Exception {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just added one test to review the IntDef usage. I will add more tests, once that is reviewed . Thank you.

2. Renaming InstallationTokenResult TokenExpirationTimestampMillis field to TokenExpirationInSecs
@ankitaj224 ankitaj224 requested a review from ciarand September 6, 2019 22:15
@@ -169,6 +180,15 @@ private static boolean persistedFidMissingOrInErrorState(PersistedFidEntry persi
};
}

@NonNull
private <F, T> Continuation<F, T> call(@NonNull Supplier<T> supplier) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

give this method a more specific name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. PTAL.

@diwu-arete diwu-arete self-requested a review September 11, 2019 20:54
@ankitaj224
Copy link
Contributor Author

Friendly ping !!

2. Minor code changes as per the offline discussion with Rayo
@VinayGuthal
Copy link
Contributor

run ./gradlew :firebase-installations:generateApiTxtFile and commit the changes to fix the api-information bug

@VinayGuthal
Copy link
Contributor

same thing with :firebase-installations-interop:apiInformation project as well

@ankitaj224
Copy link
Contributor Author

/test new-smoke-tests


@Before
public void setUp() throws FirebaseInstallationServiceException {
MockitoAnnotations.initMocks(this);
FirebaseApp.clearInstancesForTest();
executor = new ThreadPoolExecutor(0, 2, 10L, TimeUnit.SECONDS, new SynchronousQueue<>());
executor = new ThreadPoolExecutor(0, 4, 10L, TimeUnit.SECONDS, new SynchronousQueue<>());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a comment explaining why you've chosen 4 for this test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. PTAL.

Throwable cause = expected.getCause();
assertWithMessage("Exception class doesn't match")
.that(cause)
.isInstanceOf(FirebaseInstallationsException.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Truth has built-in support for unpacking exceptions

assertWithMessage("w/e").that(expected).hasCauseThat().isInstanceOf(FirebaseInstallationsException.class)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Did you also want the status to be changed? I tried but dint find a easy way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd have to create a custom truth subject for that and tbh I don't think it's worth it atm


@Test
public void testGetAuthToken_unregisteredFid_fetchedNewTokenFromFIS() throws Exception {
when(mockPersistedFid.readPersistedFidEntryValue())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo this test could do with some comments explaining what you're testing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. PTAL.

firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH);
Thread.sleep(500);
Task<InstallationTokenResult> task2 =
firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't have to sleep in tests. Can we somehow use the injected to ensure these tasks are run?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted the sleep to ensure task executed in order. But I removed it, instead of validating on both expected values

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't they execute in order at the moment? Is it because the executor has 4 threads? Could we use a single threaded executor to make this deterministic?


class ServiceClient extends FirebaseInstallationServiceClient {

private boolean secondCall;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be explained or reworked imo, it's not super obvious what this helper and its test are checking.

Could you use an inline mockito answer (thenAnswer instead of thenThrow / thenReturn) to clarify the goal of the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I have tried using thenAnswer. PTAL.

final class AwaitListener implements OnCompleteListener<Void> {
private final CountDownLatch mLatch = new CountDownLatch(1);

public void onSuccess() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't actually used, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its used. If the status is REGISTERED, instead of waiting for 10s, I am using this method to update the awaitlistener.

FirebaseInstalllations.java line 261

@NonNull Supplier<T> supplier, AwaitListener listener) {
return t -> {
// Waiting for Task that registers FID on the FIS Servers
listener.await(10, TimeUnit.SECONDS);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this to a constant imo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@ankitaj224 ankitaj224 requested a review from ciarand September 13, 2019 23:21
@ankitaj224
Copy link
Contributor Author

/test check-changed

Throwable cause = expected.getCause();
assertWithMessage("Exception class doesn't match")
.that(cause)
.isInstanceOf(FirebaseInstallationsException.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd have to create a custom truth subject for that and tbh I don't think it's worth it atm

// and verify one task waits for another task to complete.

doAnswer(
new AnswersWithDelay(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure here, but I think we're supposed to use the AdditionalAnswers class to create these instead of reaching into the mockito internal package: https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/AdditionalAnswers.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the link. I dint realize it was internal package. Updated.

firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH);
Thread.sleep(500);
Task<InstallationTokenResult> task2 =
firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't they execute in order at the moment? Is it because the executor has 4 threads? Could we use a single threaded executor to make this deterministic?

@ankitaj224 ankitaj224 requested a review from ciarand September 16, 2019 18:58
@@ -480,10 +476,10 @@ public void testGetAuthToken_multipleCallsForceRefresh_fetchedNewTokenTwice() th
// As we cannot ensure which task got executed first, verifying with both expected values
assertWithMessage("Persisted Auth Token doesn't match")
.that(task1.getResult().getToken())
.isAnyOf(TEST_AUTH_TOKEN_3, TEST_AUTH_TOKEN_4);
.isEqualTo(TEST_AUTH_TOKEN_3);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

@ankitaj224 ankitaj224 merged commit 6a013fa into fis_sdk Sep 16, 2019
@rlazo rlazo deleted the fis_auth branch October 3, 2019 14:44
@firebase firebase locked and limited conversation to collaborators Oct 17, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants